Programming Level-up
Lecture 1 - Introduction and Basic Python Programming
Table of Contents
- 1. Introduction
- 2. Python
- 2.1. Introducing Python
- 2.2. Types of data
- 2.3. Working with strings
- 2.3.1. Formatting strings
- 2.3.2. Better ways of printing strings - %
- 2.3.3. Better ways of printing strings – data specifiers
- 2.3.4. Better ways of printing strings – floating points
- 2.3.5. Better ways of printing strings – floating points
- 2.3.6. Better ways of printing strings –
f-strings
- 2.3.7. Better ways of printing strings –
f-strings
- 2.3.8. Better ways of printing strings –
f-string
- 2.3.9. Operations on strings – splitting
- 2.3.10. Operations on strings – joining
- 2.3.11. Operations on strings – changing case
- 2.3.12. Operations on strings – replacing strings
- 2.4. Compound data structures
- 2.4.1. Container data types/Data structures
- 2.4.2. An aside on Terminology
- 2.4.3. Lists
- 2.4.4. Better indexing – slices
- 2.4.5. Better indexing – range
- 2.4.6. Better indexing – reverse
- 2.4.7. Better indexing – range
- 2.4.8. Better indexing – backwards
- 2.4.9. Better indexing –backwards
- 2.4.10. Lists – adding data
- 2.4.11. Dictionaries
- 2.4.12. Dictionaries
- 2.4.13. Dictionaries – adding data
- 2.4.14. Dictionaries – Quick Exercise
- 2.4.15. Tuples
- 2.4.16. Tuples
- 2.4.17. Sets
- 2.4.18. Sets – adding data
- 2.5. Conditional expressions
- 2.6. Iteration
- 2.6.1. For loop
- 2.6.2. For loop – break
- 2.6.3. For loop – continue
- 2.6.4. For loop – ranges
- 2.6.5. For loop – loop over collections
- 2.6.6. For loop – loop over collections
- 2.6.7. For loop – loop over collections
- 2.6.8. For loop – loop over collections
- 2.6.9. For loop – List comprehensions
- 2.6.10. For loop – List comprehensions – syntax
- 2.6.11. For loop – List comprehensions – using
if
's - 2.6.12. For loop – List comprehension – using
if
's - 2.6.13. For loop – List comprehensions – multiple
for
's - 2.6.14. For loop – List comprehensions – dictionary
- 2.6.15. For loop – Quick Exercise
- 2.6.16. While loop
- 2.6.17. While loop
- 2.7. Functions
- 2.7.1. Functions
- 2.7.2. Functions
- 2.7.3. Example usage of a function
- 2.7.4. Functions – Quick Exercise
- 2.7.5. Re-usability with Functions
- 2.7.6. Re-usability with Functions
- 2.7.7. Named parameters
- 2.7.8. Named parameters
- 2.7.9. Optional/Default/Positional arguments
- 2.7.10. Optional/Default/Positional arguments
- 2.7.11. Optional/Default/Positional arguments
- 2.7.12. Recap on arguments
- 2.7.13. Function doc-strings
- 2.7.14. Multi-line docstrings
- 2.7.15. Understanding scope
- 2.7.16. Understanding scope
- 2.7.17. Understanding scope
- 2.7.18. Understanding scope
- 3. Exercise
1. Introduction
1.1. Course introduction
1.1.1. What…? Why…?
- Programming is much more than the act of programming a small script. Even if you've programmed before, doing so for a research project requires a lot of rigour to ensure the results you're reporting are correct, and reproducible.
- There is so much surrounding the act of programming that it can get a little overwhelming. Things from setting up a programming environment to managing multiple experiments on the supercomputers can involve many languages and understanding of technologies.
- This course is designed to take you from not being able to program at all to being able to do it comfortably for your research and work.
1.1.2. What is this course going to teach me?
- Programming with the Python Programming Language.
- Basic syntax.
- Introduction to the basics of object oriented programming (OOP).
- Numerical computing with numpy/pandas/scipy.
- Doing your programming in a Linux-based Environment (GNU/Linux) and being
comfortable with the organisation of this Linux environment.
- Setting up a research (reproducible) environment.
- Executing experiments.
- Interacting with the Super-computers/clusters.
- Interaction with SLURM (management of jobs).
- Taking the results from a program you've created, be able to visualise them and
include them in reports/papers.
- LaTeX/Markdown.
- Plotting.
1.1.3. How the course will be delivered
- 2/3 hour sessions over the next 2 months.
- Throughout the lecture, there will be small exercises to try out what we've learnt. We will go through the answers to these exercises.
- At the end of the lecture we will have a larger exercise that will become more challenging. These exercises are not marked, but again, just an opportunity to try out what you've learnt. The best way to learn how to program is to program.
1.1.4. Rough timeline
Lecture | Topic | Description |
---|---|---|
1 | Introduction | - Course introduction |
- Basic Python programming | ||
2 | Python classes | - Introduction to OOP |
3 | Project management | - Creating/importing modules |
- Anaconda/pip | ||
4 | Programming environments | - PyCharm |
- Jupyter notebooks | ||
5 | Numerical computing | - Numpy |
- Scipy | ||
6 | Numerical computing | - Pandas |
- Visualisations | ||
7 | Basics of GNU/Linux | - Using the terminal |
8 | Bash scripting | - Bash scripting |
9 | High performance computing | - SLURM |
- Singularity | ||
10 | Reporting | - LaTeX |
- Markdown |
1.2. Contact information
1.2.1. Where to find me
My name is Dr Jay Morgan. I am a researcher work on Deep Learning in Astrophysics.
- Email:
jay.morgan@univ-tln.fr
- Lecture slides and other contact on my website: https://pageperso.lis-lab.fr/jay.morgan/
2. Python
2.1. Introducing Python
2.1.1. Python
2.1.2. Python
- Python BMCOL
- Python is a high-level\footnoteframe{As we go through our lectures we'll understand what it means for the language to be /high-level/ and /interpreted/ and why that is helpful for us.} programming language created in 1991.
- While it is an old language, its become vastly popular thanks to its use in data science and other mathematics-based disciplines. While also being able to perform tasks such as GUI, web-development and much more.
- Because the language is high-level and interpreted, programmers can often find themselves more productive in Python than in other languages such as say C++.
- Python logo BMCOL
2.1.3. A first program
We're going to start with the 'Hello, World' program that prints Hello, World!
to the
screen. In python this is as simple as writing:
print("Hello, World!") # this prints: Hello, World!
Results: # => Hello, World!
NOTE anything following a #
is a comment and is completely ignored by the
computer. It is there for you to document your code for others, and most
importantly, for yourself.
2.1.4. Running this program
Before we can run this program, we need to save it somewhere. For this, will
create a new file, insert this text, and save it as <filename>.py
, where
<filename>
is what we want to call the script. This name doesn't matter for its
execution.
Once we have created the script, we can run it from the command line. We will get into the command line in a later lecture, but right now all you need to know is:
python3 <filename>.py
2.1.5. An alternative method of running python
You may notice that if you don't give python
a filename to run, you will enter
something called the REPL
.
Python 3.9.5 (default, Jun 4 2021, 12:28:51) [GCC 7.5.0] :: Anaconda, Inc. on linux Type "help", "copyright", "credits" or "license" for more information. >>>
REPL
stands for READ
, EXECUTE
, PRINT
, LOOP
.
2.1.6. Variables
A variable is a symbol associated with a value. This value can differ widely, and we will take a look at different types of values/data later.
Neverthless, variables are useful for referring to values and storing to the results of a computation.
x = 1 y = 2 z = x + y print(z) # prints: 3 # variables can be /overwritten/ z = "hello, world" print(z) # prints: hello, world
Results: # => 3 # => hello, world
2.2. Types of data
2.2.1. Primitive data types
Primitive data types are the most fundamental parts of programming, they cannot be broken down.
"Hello" # string 1 # integer 1.0 # float True # Boolean (or bool for short)
2.2.2. Primitive data type
We can get the type of some data by using the type(...)
function. For example,
print(type(5)) print(type(5.0)) x = "all cats meow" print(type(x))
Results: # => <class 'int'> # => <class 'float'> # => <class 'str'>
2.2.3. Basic Math with primitives
Using these primitive data types, we can do some basic math operations!
print(1 + 2) # Addtion print(1 - 2) # Subtraction print(1 * 2) # Multiplication print(1 / 2) # Division print(2 ** 2) # Exponent print(3 % 2) # Modulo operator
Results: # => 3 # => -1 # => 2 # => 0.5 # => 4 # => 1
2.2.4. Basic Math
Sometimes types get converted to the same type:
print(1.0 + 2) # float + integer = float
Results: # => 3.0
Even more interesting is with Booleans!
True + True
Results: # => 2
2.2.5. BODMAS in Python
Like in mathematics, certain math operator take precedence over others.
- B - Brackets
- O - Orders (roots, exponents)
- D - division
- M - multiplication
- A - addition
- S - subtraction.
To make the context clear as to what operations to perform first, use brackets.
(5 / 5) + 1 5 / (5 + 1)
Results: # => 2.0 # => 0.8333333333333334
2.2.6. Basic Math – Quick exercise
Write the following equation in python:
\((5 + 2) \times (\frac{10}{2} + 10)^2\)
Remember to use parentheses ( )
to ensure that operations take precedence over
others.
Your answer should come out as: 1575.0
2.3. Working with strings
2.3.1. Formatting strings
In many previous examples when we've printed strings, we've done something like:
age = 35 print("The value of age is", age)
Results: # => The value of age is 35
While this works in this small context, it can get pretty cumbersome if we have many variables we want to print, and we also want to change how they are displayed when they are printed.
We're going to take a look now at much better ways of printing.
2.3.2. Better ways of printing strings - %
The first method is using %
. When we print, we first construct a string with special
delimiters, such as %s
that denotes a string, and %d
that denotes a number. This is
telling Python where we want the values to be placed in the string.
Once we've created the string, we need to specify the data, which we do with %
(...)
. Like, for example:
age = 35 name = "John" print("%d years old" % age) # no tuple for one variable print("%s is %d years old" % (name, age))
Results: # => 35 years old # => John is 35 years old
Here we are specifying the a string %s
and number %d
, and then giving the variables
that correspond with that data type.
2.3.3. Better ways of printing strings – data specifiers
The special delimiters correspond with a data type. Here are some of the most common:
%s
– For strings%d
– For numbers%f
– For floating point numbers.
There are others such as %x
that prints the hexadecimal representation, but these are
less common. You can find the full list at: https://docs.python.org/3/library/stdtypes.html#old-string-formatting
2.3.4. Better ways of printing strings – floating points
When using these delimiters, we can add modifiers to how they format and display the
value. Take a very common example, where we have a floating point value, and, when
printing it, we only want to print to 3 decimal places. To accomplish this, we again
use %f
but add a .3
to between the %
and f
. In this example, we are printing π to 3
decimal places.
print("Pi to 3 digits is: %.3f" % 3.1415926535)
Results: # => Pi to 3 digits is: 3.142
2.3.5. Better ways of printing strings – floating points
In the previous example, we used .3
to specify 3 decimal places. If we put a number
before the decimal, like 10.3
we are telling Python make this float occupy 10 spaces
and this float should have 3 decimal places printed. When it gets printed, you will
notice that it shifts to the right, it gets padded by space. If we use a negative
number in front of the decimal place, we are telling python to shift it to the left.
print("Pi to 3 digits is: %10.3f" % 3.1415926535) print("Pi to 3 digits is: %-10.3f" % 3.1415926535)
Results: # => Pi to 3 digits is: 3.142 # => Pi to 3 digits is: 3.142
2.3.6. Better ways of printing strings – f-strings
The final method of formatting strings is a newcomer within the language, it is the
so-called f-string
. Where a f
character is prefixed to the beginning of the string
you're creating. f-string
's allow you to use Python syntax within the string (again
delimited by {}
.
Take this for example where we are referencing the variables name
and age
directly.
name = "Jane" age = 35 print(f"{name} is {age} years old")
Results: # => Jane is 35 years old
2.3.7. Better ways of printing strings – f-strings
f-string
's allow you to execute Python code within the string. Here we are accessing
the value from the dictionary by specifying the key within the string itself! It
certainly makes it a lot easier, especially if we only need to access the values for
the string itself.
contact_info = {"name": "Jane", "age": 35} print(f"{contact_info['name']} is {contact_info['age']} years old")
Results: # => Jane is 35 years old
2.3.8. Better ways of printing strings – f-string
We can still format the values when using f-string
. The method is similar to those
using the %f
specifiers.
pi = 3.1415926535 print(f"Pi is {pi:.3f} to 3 decimal places")
Results: # => Pi is 3.142 to 3 decimal places
Many more examples can be found at: https://zetcode.com/python/fstring/
2.3.9. Operations on strings – splitting
Apart from formatting, there are plenty more operations we can perform on strings. We are going to highlight some of the most common here.
The first we're going to look at is splitting a string by a delimiter character using
the .split()
method. If we don't pass any argument to the .split()
method, then by
default, it will split by spaces. However, we can change this by specifying the
delimiter.
my_string = "This is a sentence, where each word is separated by a space" print(my_string.split()) print(my_string.split(","))
Results: # => ['This', 'is', 'a', 'sentence,', 'where', 'each', 'word', 'is', 'separated', 'by', 'a', 'space'] # => ['This is a sentence', ' where each word is separated by a space']
2.3.10. Operations on strings – joining
As .split()
splits a single string into a list, .join()
joins a list of strings into
a single string. To use .join()
, we first create a string of the delimiter we want to
use to join the list of strings by. In this example we're going to use "-"
. Then we
call the .join()
method, passing the list as an argument.
The result is a single string using the delimiter to separate the items of the list.
x = ['This', 'is', 'a', 'sentence,', 'where', 'each', 'word', 'is', 'separated', 'by', 'a', 'space'] print("-".join(x))
Results: # => This-is-a-sentence,-where-each-word-is-separated-by-a-space
2.3.11. Operations on strings – changing case
Other common operations on strings involve change the case. For example:
- Make the entire string uppercase or lowercase
- Making the string title case (every where starts with a capital letter).
- Stripping the string by removing any empty spaces either side of the string.
Note we can chain many methods together by doing .method_1().method_2()
, but only if they
return string. If they return None
, then chaining will not work.
x = " this String Can change case" print(x.upper()) print(x.lower()) print(x.title()) print(x.strip()) print(x.strip().title())
Results: # => THIS STRING CAN CHANGE CASE # => this string can change case # => This String Can Change Case # => this String Can change case # => This String Can Change Case
2.3.12. Operations on strings – replacing strings
To replace a substring, we use the .replace()
method. The first argument is the old
string you want to replace. The second argument is what you want to replace it with.
x = "This is a string that contains some text" print(x.replace("contains some", "definitely contains some"))
Results: # => This is a string that definitely contains some text
2.4. Compound data structures
2.4.1. Container data types/Data structures
Container data types or data structures, as the name suggests, are used to contain other things. Types of containers are:
- Lists
- Dictionaries
- Tuples
- Sets
[1, "hello", 2] # list {"my-key": 2, "your-key": 1} # dictionary (or dict) (1, 2) # tuple set(1, 2) # set
We'll take a look at each of these different container types and explore why we might want to use each of them.
2.4.2. An aside on Terminology
To make our explanations clearer and reduce confusion, each of the different symbols have unique names.
I will use this terminology consistently throughout the course, and it is common to see the same use outside the course.
[ ]
brackets (square brackets).{ }
braces (curly braces).( )
parentheses.
2.4.3. Lists
A hetreogenious container. This means that it can store any type of data.
x = [1, "hello", 2]
Elements can be accessed using indexing [ ]
notation. For example:
print(x[0]) # this will get the first element (i.e. 1) print(x[1]) # the second element (i.e. "hello") print(x[2]) # the third element (i.e. 2)
Results: # => 1 # => hello # => 2
notice how the first element is the 0-th item in the list/ we say that python is 0-indexed.
2.4.4. Better indexing – slices
If we wanted to access an element from a data structure, such as a list, we would use
the [ ]
accessor, specifying the index of the element we wish to retrieve (remember
that indexes start at zero!). But what if we ranted to access many elements at once?
Well to accomplish that, we have a slice or a range of indexes (not to be confused
with the range
function). A slice is defined as:
start_index:end_index
where the end_index
is non inclusive – it doesn't get included in the result. Here
is an example where we have a list of 6 numbers from 0 to 5, and we slice the list from index
0 to 3. Notice how the 3rd index is not included.
x = [0, 1, 2, 3, 4, 5] print(x[0:3])
Results: # => [0, 1, 2]
2.4.5. Better indexing – range
When we use start_index:end_index
, the slice increments by 1 from start_index
to
end_index
. If we wanted to increment by a different amount we can use the slicing
form:
start_index:end_index:step
Here is an example where we step the indexes by 2:
x = list(range(100)) print(x[10:15:2])
Results: # => [10, 12, 14]
2.4.6. Better indexing – reverse
One strange fact about the step is that if we specify a negative number for the step, Python will work backwards, and effectively reverse the list.
x = list(range(5)) print(x[::-1])
Results: # => [4, 3, 2, 1, 0]
2.4.7. Better indexing – range
In a previous example, I created a slice like 0:3
. This was a little wasteful as we
can write slightly less code. If we write :end_index
, Python assumes and creates a
slice from the first index (0) to the end_index
. If we write start_index:
, Python
assumes and creates a slice from start_index
to the end of the list.
x = list(range(100)) print(x[:10]) print(x[90:])
Results: # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # => [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
2.4.8. Better indexing – backwards
Finally, we also work backwards from the end of list. If we use a negative number, such as -1, we are telling Python, take the elements from the end of the list. -1 is the final index, and numbers lower than -1 work further backwards through the list.
x = list(range(100)) print(x[-1]) print(x[-2])
Results: # => 99 # => 98
2.4.9. Better indexing –backwards
Slicing with negative indexes, also works. Here we are creating a slice from the end of the list - 10, to the last (but not including) index.
x = list(range(100)) print(x[-10:-1])
Results: # => [90, 91, 92, 93, 94, 95, 96, 97, 98]
2.4.10. Lists – adding data
If we want to add items to the end of the list, we use the append
function:
my_list = [] my_list.append("all") my_list.append("dogs") my_list.append("bark") print(my_list)
Results: # => ['all', 'dogs', 'bark']
2.4.11. Dictionaries
Dictionaries are a little different from lists as each 'element' consists of a key-pair value. Let's have a look at some examples where the dictionaries contains one element:
my_dictionary = {"key": "value"} my_other_dict = {"age": 25}
To access the value, we get it using [key]
notation:
my_other_dict["age"]
Results: # => 25
NOTE keys are unique, i.e:
my_dictionary = {"age": 25, "age": 15} my_dictionary["age"]
Results: # => 15
2.4.12. Dictionaries
The key in the dictionary doesn't necessarily need to be a string. For example, in this case, we have created two key-pair elements, where the keys to both are tuples of numbers.
my_dictionary = {(1, 2): "square", (3, 4): "circle"} print(my_dictionary[(1, 2)])
Results: # => square
2.4.13. Dictionaries – adding data
If we want to add data to a dictionary, we simply perform the accessor method with a key that is not in the dictionary:
my_dict = {} my_dict["name"] = "James" my_dict["age"] = 35 print(my_dict)
Results: # => {'name': 'James', 'age': 35}
2.4.14. Dictionaries – Quick Exercise
- Create a dictionary for the following address, and assign it a variable name
called
address
:
Key | Value |
---|---|
number | 22 |
street | Bakers Street |
city | London |
- Print out the address's street name using the
[ ]
accessor with the correct key.
2.4.15. Tuples
my_tuple = (1, 56, -2)
Like lists, elements of the tuple can be accessed by their position in the list, starting with the 0-th element:
print(my_tuple[0]) # => 1 print(my_tuple[1]) # => 56 print(my_tuple[2]) # => -2
Results: # => 1 # => 56 # => -2
2.4.16. Tuples
Unlike lists, tuples cannot be changed after they've been created. We say they are immutable. So this will not work:
my_tuple[2] = "dogs" # creates an Error
Results: # => Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/tmp/pyKdIIcx", line 18, in <module> File "<string>", line 1, in <module> TypeError: 'tuple' object does not support item assignment
2.4.17. Sets
Sets in Python are like tuples, but contain only unique elements.
You can use the set( )
function (more on functions later!), supplying a list, to create a set:
my_set = set([1, 2, 2, 2, 3, 4]) my_set
Results: # => {1, 2, 3, 4}
Notice how there is only one '2' in the resulting set, duplicate elements are removed.
2.4.18. Sets – adding data
If we want to add data to a set, we use the .add()
method. The element used as an
argument to this function will only be added to the set if it is not already in the
set.
my_set = set([]) my_set.add(1) my_set.add(2) my_set.add(1) print(my_set)
Results: # => {1, 2}
2.5. Conditional expressions
2.5.1. If statement
If statements allow for branching paths of execution. In other words, we can execute some statements if some conditions holds (or does not hold).
The structure of a simple if statement is:
if <condition>:
<body>
x = 2 y = "stop" if x < 5: print("X is less than five") if y == "go": print("All systems go!!")
Results: # => X is less than five
2.5.2. If statement
In the previous example, the first print
statement was only executed if the x < 5
evaluates to True
, but in python, we can add another branch if the condition
evaluates to False
. This branch is denoted by the else
keyword.
x = 10 if x < 5: print("X is less than five") else: print("X is greater than or equal to five")
Results: # => X is greater than or equal to five
2.5.3. If statement – does it contain a substring?
We can check if a string exists within another string using the in
keyword. This
returns a Boolean value, so we can use it as a condition to an if
statement.
x = "This is a string that contains some text" if "text" in x: print("It exists")
Results: # => It exists
2.5.4. If statement – Quick Exercise 1
- Create a variable called
age
and assign the value of this variable35
. - Create and
if
statement that prints the square ofage
if the value ofage
is more than 24. - This if statement should have an else condition, that prints
age
divided by 2. - What is the printed value?
2.5.5. If statement
If we wanted to add multiple potential paths, we can add more using the elif
<condition>
keywords.
Note: The conditions are checked from top to bottom, only executing the else if none
evaluate to True
. The first condition that evaluates to True
is executed, the rest
are skipped.
x = 15 if x < 5: print("X is less than five") elif x > 10: print("X is greater than ten") else: print("X is between five and ten")
Results: # => X is greater than ten
2.5.6. If statement
Sometimes, we might want to conditionally set a variable a value. For this, we can use an inline if statement. The form of an inline if statement is:
<value-if-true> if <condition> else <value-if-false>
x = 10 y = 5 if x > 5 else 2 print(x + y)
Results: # => 15
2.5.7. Boolean Logic
As we've seen, if
statements are checking for conditions to evaluate to True
or
False
. In python we use various comparison operators to check for conditions that
evaluate to Booleans
.
Comparison operators
<
less than<=
less than or equal to>
greater than>=
greater than or equal to==
is equal tonot
negation
If we want to check for multiple conditions, we can use conjunctives or disjunctive operators to combine the Boolean formulas.
Conjunctives/Disjunctives
and
all boolean expressions must evaluate to trueor
only one expression needs to be true
2.5.8. Boolean Logic
Using not
you can invert the Boolean result of the expression.
print(not True)
Results: # => False
x = 10 if not x == 11: print("X is not 11")
Results: # => X is not 11
2.5.9. Boolean Logic
Let's take an example using the and
keyword. and
here is checking that x
is above or
equal to 10 and y
is exactly 5. If either of the conditions is False
, python will
execute the else
path (if there is one, of course!).
x = 10 y = 5 if x >= 10 and y == 5: z = x + y else: z = x * y print(z)
Results: # => 15
2.5.10. Boolean Logic
Here we see the use of the or
keyword. If any of the conditions evaluates to True
then the whole condition evaluates to True
.
x = 10 y = 5 if x < 5 or y == 5: print("We got here!") else: print("We got here instead...")
Results: # => We got here!
2.5.11. Boolean Logic
Note: or
is short-circuiting. This means that if tests the conditions left-to-right,
and when it finds something that is True
it stops evaluating the rest of the
conditions.
x = 10 if x < 20 or print("We got to this condition"): print("The value of x is", x)
Results: # => The value of x is 10
2.5.12. Boolean Logic
If your Boolean logic refers to a single variable, you can combine the logic without
the and
and or
. But its not always common.
For example,
x = 7 if x < 10 and x > 4: print("X is between 5 and 10")
Can be the same as:
x = 7 if 5 < x < 10: print("X is between 5 and 10")
Results: # => X is between 5 and 10
2.6. Iteration
2.6.1. For loop
Looping or iteration allows us to perform a series of actions multiple times. We are
going to start with the more useful for
loop in python. The syntax of a for
loop is:
for <variable_name> in <iterable>: <body>
for i in range(3): print(i)
Results: # => 0 # => 1 # => 2
2.6.2. For loop – break
The previous example loops over the body a fix number of times. But what if we wanted
to stop looping early? Well, we can use the break
keyword. This keyword will exit the
body of the loop.
for i in range(10): if i > 5: break print(i)
Results: # => 0 # => 1 # => 2 # => 3 # => 4 # => 5
2.6.3. For loop – continue
A different keyword you might want to use is continue
. Continue allows you to move/skip
onto the next iteration without executing the entire body of the for
loop.
for i in range(10): if i % 2 == 0: continue print(i)
Results: # => 1 # => 3 # => 5 # => 7 # => 9
2.6.4. For loop – ranges
Instead of using continue
like in the previous slide, the range
function provides us
with some options:
range(start, stop, step)
In this example, we are starting our iteration at 10, ending at 15, but stepping the counter 2 steps.
for i in range(10, 15, 2): print(i)
Results: # => 10 # => 12 # => 14
2.6.5. For loop – loop over collections
For loops allow us to iterate over a collection, taking one element at a time. Take for example, a list, and for every item in the list we print its square.
my_list = [1, 5, 2, 3, 5.5] for el in my_list: print(el**2)
Results: # => 1 # => 25 # => 4 # => 9 # => 30.25
2.6.6. For loop – loop over collections
This kind of looping can work for tuples and sets, but as we have seen, dictionaries are a little different. Every 'element' in a dictionary consists of a key and a value. Therefore when we iterate over items in a dictionary, we can assign the key and value to different variables in the loop.
Note the use of the .items()
after the dictionary. We will explore this later.
my_dict = {"name": "jane", "age": 35, "loc": "France"} for el_key, el_val in my_dict.items(): print("Key is:", el_key, " value is: ", el_val)
Results: # => Key is: name and the value is: jane # => Key is: age and the value is: 35 # => Key is: location and the value is: France
2.6.7. For loop – loop over collections
We could also loop over the keys in the dictionary using the .keys()
method instead
of .items()
.
my_dict = {"name": "jane", "age": 35, "loc": "France"} for the_key in my_dict.keys(): print(the_key)
Results: # => name # => age # => loc
2.6.8. For loop – loop over collections
Or, the values using .values()
.
my_dict = {"name": "jane", "age": 35, "loc": "France"} for the_value in my_dict.values(): print(the_value)
Results: # => jane # => 35 # => France
2.6.9. For loop – List comprehensions
We have seen previously how for
loops work. Knowing the syntax of a for
loop and
wanting to populate a list with some data, we might be tempted to write:
x = [] for i in range(3): x.append(i) print(x)
Results: # => [0, 1, 2]
While this is perfectly valid Python code, Python itself provides 'List comprehensions' to make this process easier.
x = [i for i in range(3)]
2.6.10. For loop – List comprehensions – syntax
The syntax of a list comprehensions is:
[ <variable> for <variable> in <iterable> ]
We can also perform similar actions with a dictionary
[ <key>, <value> for <key>, <value> in <dictionary.items()> ]
2.6.11. For loop – List comprehensions – using if
's
Perhaps we only want to optionally perform an action within the list comprehension?
Python allows us to do this with the inline if
statement we've seen in the previous lecture.
x = [i if i < 5 else -1 for i in range(7)] print(x)
Results: # => [0, 1, 2, 3, 4, -1, -1]
We add the inline <var> if <condition> else <other-var>
before the for
loop part of
the comprehension.
2.6.12. For loop – List comprehension – using if
's
There is another type of if
statement in a list comprehension, this occurs when we
don't have an else
.
x = [i for i in range(7) if i < 3] print(x)
Results: # => [0, 1, 2]
In this example, we're only 'adding' to the list if the condition (\(i < 3\)) is true, else the element is not included in the resulting list.
2.6.13. For loop – List comprehensions – multiple for
's
If we like, we can also use nested for loops by simply adding another for loop into the comprehension.
x = [(i, j) for i in range(2) for j in range(2)] print(x)
Results: # => [(0, 0), (0, 1), (1, 0), (1, 1)]
In this example, we're creating a tuple for each element, effectively each combination of 1 and 0.
2.6.14. For loop – List comprehensions – dictionary
Python doesn't restrict us to list comprehensions, but we can do a similar operation to create a dictionary.
x = [2, 5, 6] y = {idx: val for idx, val in enumerate(x)} print(y)
Results: # => {0: 2, 1: 5, 2: 6}
Here, every item in x
has been associated with its numerical index as a key thanks to
the enumerate
function that returns both the index and value at iteration in the for loop.
2.6.15. For loop – Quick Exercise
- Create a list of elements:
- 2
- "NA"
- 24
- 5
- Use a
for
loop to iterate over this list. - In the body of the
for
loop, compute \(2x + 1\), where \(x\) is the current element of the list. - Store the result of this computation in a new variable \(y\), and then print y.
Note You cannot compute \(2x + 1\) of "NA", therefore you will to use an if
statement
to skip onto the next iteration if it encounters this. Hint try: type(...) =!=
str
2.6.16. While loop
A while
loop is another looping concept like for
but it can loop for an arbitrary
amount of times. A while
loop looks to see if the condition is True
, and if it is, it
will execute the body.
The syntax of the while loop is:
while <condition>:
<body>
i = 0 while i < 3: print(i) i = i + 1
Results: # => 0 # => 1 # => 2
2.6.17. While loop
x = 0 y = 1 while x + y < 10: print("X is,", x, "and y is", y) x = x + 1 y = y * 2 print("X ended as", x, ", while y is", y)
Results: # => X is, 0 and y is 1 # => X is, 1 and y is 2 # => X is, 2 and y is 4 # => X ended as 3 , while y is 8
2.7. Functions
2.7.1. Functions
Functions are a re-usable set of instructions that can take some arguments and possible return something.
The basic structure of a function is as follows:
def <function_name>(args*): <body> (optional) return
args*
are 0 to many comma separated symbols.body
is to be indented by 4 spaces.
This is only the function definition however. To make it do something, we must 'call' the function, and supply the arguments as specified in the definition.
def say_hello(): # function definition print("Hello, World!") say_hello() # calling the function
2.7.2. Functions
We've already seen some functions provided by Python.
print
itself is a function with a single argument: what we want to print.
print("Hello, World!") # ^ ^ # | | # | user supplied argument # | # function name
set
is another function that takes a single argument: a collection of data with which
to make a set:
set([1, 2, 2, 3, 4])
2.7.3. Example usage of a function
Let's make a function that takes two numbers and adds them together:
def my_addition(a, b): result = a + b return result x = 2 y = 3 z = my_addition(2, 3) # return 5 and stores in z print(z)
Results: # => 5
2.7.4. Functions – Quick Exercise
- Create a function called
my_square
. This function should take one argument (you can call this argument what you like). - The body of the function should compute and return the square of the argument.
- Call this function with
5.556
. - Store the result of calling this function, and print it.
- What is the result?
2.7.5. Re-usability with Functions
Functions are better illustrated through some examples, so let's see some!
name_1 = "john" name_2 = "mary" name_3 = "michael" print("Hello " + name_1 + ", how are you?") print("Hello " + name_2 + ", how are you?") print("Hello " + name_3 + ", how are you?")
The above is pretty wasteful. Why? Because we are performing the exact same operation multiple times, with only the variable changed.
2.7.6. Re-usability with Functions
By abstracting the actions we want to perform into a function, we can ultimately reduce the amount of code we write. Be a lazy programmer!
name_1 = "john" name_2 = "mary" name_3 = "michael" def say_hello(name): print("Hello " + name + ", how are you?") say_hello(name_1) say_hello(name_2) say_hello(name_3)
In this example, we've used the function as defined with the def
pattern to write
the print
statement once. Then, we've called the function with each variable as its
argument.
2.7.7. Named parameters
We've seen in previous examples that, when we create a function, we give each of the arguments (if there are any) a name.
When calling this function, we can specify these same names such as:
def say_hello(name): print("Hello,", name) say_hello("Micheal") say_hello(name="Micheal")
Results: # => Hello, Micheal # => Hello, Micheal
2.7.8. Named parameters
By specifying the name of the parameter we're using with the called function, we can change the order
def say_greeting(greeting, name): print(greeting, name, "I hope you're having a good day") say_greeting(name="John", greeting="Hi")
Results: # => Hi John I hope you're having a good day
2.7.9. Optional/Default/Positional arguments
When we call a function with arguments without naming them, we are supplying them by position.
def say_greeting(greeting, name): print(greeting, name, "I hope you're having a good day") say_greeting(#first position, #section position)
The first position gets mapped to variable name of greeting
inside the body of the
say_greeting
function, while the second position gets mapped to name
.
2.7.10. Optional/Default/Positional arguments
Sometimes when creating a function we may want to use default arguments, these are arguments that are used if the call to the function does not specify what their value should be. For example.
def say_greeting(name, greeting="Hello"): print(greeting, name, "I hope you're having a good day") say_greeting("John") say_greeting("John", "Hi") # supply greeting as positional argument
Results: # => Hello John I hope you're having a good day # => Hi John I hope you're having a good day
2.7.11. Optional/Default/Positional arguments
Note if you supply a default argument in the function definition, all arguments after this default argument must also supply a default argument.
So, this won't work:
def say_greeting(name="Jane", greeting): print(greeting, name, "I hope you're having a good day") say_greeting("John", "Hi")
2.7.12. Recap on arguments
# defining the function def say_greeting(name, greeting) # no default arguments def say_greeting(name, greeting="Hello") # greeting is a default argument def say_greeting(name="Jane", greeting="Hello") # both arguments have a default # calling the functions say_greeting("John", "Hi") # both arguments are provided by position say_greeting(name="John", greeting="Hi") # arguments are supplied by name say_greeting(greeting="Hi", name="John") # the position of named arguments do not matter
2.7.13. Function doc-strings
To make it clear for a human to quickly understand what a function is doing, you can add an optional doc-string. This is a string that is added directly after the initial definition of the function:
def my_function(x, y): """I am a docstring!!!""" return x + y
Some common use cases for docstrings are explaining what the parameters are that it expects, and what it returns.
2.7.14. Multi-line docstrings
If your explanation is a little longer than a line, a multiline docstring can be
created as long as you're using """
three quotation marks either side of the string
def my_function(x, y): """ This is my realllly long docstring that explains how the function works. But sometimes its best not to explain the obvious """ return x + y
2.7.15. Understanding scope
In this example we have two scopes which can be easily seen by the indentation. The first is the global scope. The second scope is the scope of the function. The scope of the function can reference variables in the larger scope. But once the function scope exits, we can no longer reference the variables from the function.
x = 10 def compute_addition(y): return x + y print(compute_addition(10)) print(x) print(y) # does not work
Results: # => 20 # => 10
2.7.16. Understanding scope
Even though we can reference the global scope variable from the scope of the function, we can't modify it like this:
x = 10 def compute_addition_2(y): x = x + 5 # error local variable referenced before assignment return x + y print(compute_addition_2(10))
2.7.17. Understanding scope
If we really wanted to reference a variable in a global scope and modify its value,
we could use the global
keyword. Doing this makes the
function output something different every time it is called. This can make it
difficult to debug incorrect programs.
x = 10 def compute_addition_2(y): global x x = x + 5 return x + y print(compute_addition_2(10)) print(x) print(compute_addition_2(10))
Results: # => 25 # => 15 # => 30
2.7.18. Understanding scope
In almost all cases, avoid using global variables. Instead pass the variables as parameters. This can reduce a source of potential errors and ensure that if a function is called multiple times, the output can be more consistent and expected.
x = 10 def compute_addition_3(x, y): x = x + 5 return x + y print(compute_addition_3(x, 10)) print(x) print(compute_addition_3(x, 10))
Results: # => 25 # => 10 # => 25
3. Exercise
3.1. Library system
3.1.1. Use what you've learnt!
We're going to create a library system to help locate and lookup information about books. For example, we want to know the author of book called 'Moby Dick'.
To create this system, we are going to do it in stages. First, we will want to create our database of books:
Title | Author | Release Date |
---|---|---|
Moby Dick | Herman Melville | 1851 |
A Study in Scarlet | Sir Arthur Conan Doyle | 1887 |
Frankenstein | Mary Shelley | 1818 |
Hitchhikers Guide to the Galaxy | Douglas Adams | 1879 |
Our database is going to be a list of dictionaries. Where each dictionary is a row from this table. For example, one of the dictionaries will have the key "title" and a value "Moby Dick".
Create this database and call it db
.
3.1.2. Locating Books
- Create a function called
locate_by_title
that takes the database to look through, and the title to look up as arguments. - This function should check each dictionary, and if the title is the same as what was searched for, it should return the whole dictionary.
- Test this function by calling the
locate_by_title
function withdb
and"Frankenstein"
. You should get{"title": "Frankenstein", "author": ...}
.
Note you should include docstrings to describe the arguments to the function, and what it will return.
3.1.3. Selecting a subset
Now that we can find books by the title name, we also want to find all books that were released after a certain data.
- Create a function called
books_released_after
that takes two arguments: the database to look through, and the year. - This function should look through the database, if it finds a book that was released after the year, it should add it to a list of books that is returned from this function.
- Test this function by calling
books_released_after
withdb
and1850
. This function call should return a list containing three dictionaries. The first entry should be 'Moby Dick' and the section should be 'A Study in Scarlet', etc.
3.1.4. Updating our database
Oh no! 'Hitchhikers Guide to the Galaxy' was released in 1979 not 1879, there must have been a typo. Let's create a function to update this.
Create a function called
update
, that takes 5 arguments: 1) the database to update, 2) the key of the value we want to update 3) the value we want to update it to 4) the key we want to check to find out if we have the correct book and 5) the value of the key to check if we have the correct book.update(db, key="release year", value=1979, where_key="title", where_value="Hitchhikers Guide to the Galaxy")
3.1.5. Extended exercise
- In the previous steps we created functions
locate_by_title
andbooks_released_after
. These two functions are similar in a way that they are selecting a subset of our database (just by different criteria). - For this harder exercise, can we create a single function called
query
that allows us to do bothlocate_by_title
andbooks_released_after
. An example call to this
query
function may look like:results = query(db, where_key="title", where_value="Moby Dick", where_qualifier="exactly")
where_qualifier
should accept strings like"exactly"
,"greater than"
, and"less than"
.